using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.XPath;
using Microsoft.FxCop.Sdk;
using System.Xml;
using System.IO;
using BizTalkCop.Rules.Enums;
using System.Xml.Xsl;

namespace BizTalkCop.Rules.Bases
{
    /// <summary>
    /// Base class for FxCop rules running over BizTalk orchestrations
    /// </summary>
    /// <remarks>
    /// Exposes methods to access orchestration shapes and shape metadata from the XML
    /// held in the compiled type, and XPath queries to retrieve common elements and attributes
    /// </remarks>
    public abstract class OrchestrationRuleBase : ConfiguredRuleBase
    {
        #region Constructors

        /// <summary>
        /// Constructor with initial state
        /// </summary>
        /// <param name="name">Rule name, key for Rules.xml</param>
        protected OrchestrationRuleBase(string name) : base(name) { }

        #endregion

        #region Protected static methods

        /// <summary>
        /// Executes given XPath select query on given XPathNavigator, adding relevant namespaces
        /// </summary>
        /// <param name="navigator">XPathNavigator to execute query</param>
        /// <param name="xpathQuery">XPath query to execute</param>
        /// <returns>Returned node iterator</returns>
        protected static XPathNodeIterator Select(XPathNavigator navigator, string xpathQuery)
        {
            XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
            manager.AddNamespace(OMNamespace.Prefix, OMNamespace.Uri);
            return navigator.Select(xpathQuery, manager);
        }

        /// <summary>
        /// Executes given XPath single-node select query on given XPathNavigator, adding relevant namespaces
        /// </summary>
        /// <param name="navigator">XPathNavigator to execute query</param>
        /// <param name="xpathQuery">XPath query to execute</param>
        /// <returns>Returned node navigator</returns>
        protected static XPathNavigator SelectSingleNode(XPathNavigator navigator, string xpathQuery)
        {
            XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
            manager.AddNamespace(OMNamespace.Prefix, OMNamespace.Uri);
            return navigator.SelectSingleNode(xpathQuery, manager);
        }

        /// <summary>
        /// Executes the given XPath single-node query on the Shape XML in the given type
        /// </summary>
        /// <param name="type">Orchestration type</param>
        /// <param name="xpathQuery">XPath query to execute</param>
        /// <returns>Returned node navigator</returns>
        protected static XPathNavigator SelectSingleShapeNode(TypeNode type,string xpathQuery)
        {
            string processFlowXml = GetShapeXml(type);
            return SelectSingleNode(processFlowXml, xpathQuery);
        }

        /// <summary>
        /// Returns a node iterator containing the ShapeInfo elements of the given type in the Shape XML
        /// </summary>
        /// <param name="type">Orchestration type</param>
        /// <param name="shapeType">Shape type to return</param>
        /// <returns>Returned node iterator</returns>
        protected static XPathNodeIterator GetShapeInfos(TypeNode type, ShapeType shapeType)
        {
            string processFlowXml = GetShapeXml(type);
            XPathNodeIterator iterator = SelectNodes(processFlowXml, string.Format(XPathQuery.ShapeInfoFormat, shapeType));
            return iterator;
        }

        /// <summary>
        /// Returns a node iterator containing the OM elements of the given type in the OM XML
        /// </summary>
        /// <param name="type">Orchestration type</param>
        /// <param name="elementType">OM type to return</param>
        /// <returns>Returned node iterator</returns>
        protected static XPathNodeIterator GetOMElements(TypeNode type, OMElementType elementType)
        {
            string omXml = GetOMXml(type); 
            XPathNodeIterator iterator = SelectNodes(omXml, string.Format(XPathQuery.OMElementByTypeFormat, elementType));
            return iterator;
        }

        /// <summary>
        /// Returns a node iterator containing the OM element with the gievn ID in the OM XML
        /// </summary>
        /// <param name="type">Orchestration type</param>
        /// <param name="oID">OM element ID</param>
        /// <returns>Returned node navigator</returns>
        protected static XPathNavigator GetOMElement(TypeNode type, string oID)
        {
            string omXml = GetOMXml(type);
            return SelectSingleNode(omXml, string.Format(XPathQuery.OMElementByIdFormat, oID));
        }

        #endregion

        #region Private static methods

        private static XPathNodeIterator SelectNodes(string xml, string xpathQuery)
        {
            XPathNavigator navigator = GetNavigator(xml);
            return Select(navigator, xpathQuery);
        }

        private static XPathNavigator SelectSingleNode(string xml, string xpathQuery)
        {
            XPathNavigator navigator = GetNavigator(xml);
            return SelectSingleNode(navigator, xpathQuery);
        }

        private static XPathNavigator GetNavigator(string xml)
        {
            StringReader reader = new StringReader(xml);
            XPathDocument document = new XPathDocument(reader);
            return document.CreateNavigator();
        }

        private static string GetOMXml(TypeNode type)
        {
            return GetFieldDefaultValue(type, FieldName.OMXml).TrimStart(Environment.NewLine.ToCharArray());
        }

        private static string GetShapeXml(TypeNode type)
        {
            return GetFieldDefaultValue(type, FieldName.ShapeXml);
        }

        #endregion

        #region Structs

        private struct FieldName
        {
            public const string ShapeXml = "_symInfo";
            public const string OMXml = "_symODXML";
        }

        /// <summary>
        /// Struct of common XPath queries
        /// </summary>
        protected struct XPathQuery
        {
            public const string ShapeInfoFormat = "//ShapeInfo[shapeType=\"{0}\"]";            
            public const string ShapeTypeNode = "shapeType";
            public const string ShapeTextNode = "shapeText";
            public const string ShapeIdNode = "ShapeID";
            public const string GrandparentIdByIdFormat = "//ShapeInfo[ShapeID=\"{0}\"]/../../ShapeID";
            public const string TrkMetadataFormat = "//TrkMetadata[ShapeID=\"'{0}'\"]";  
            public const string MessageOutNameNode = "Messages/MsgInfo[direction=\"Out\"]/name";
            public const string MessageOutSchemaNode = "Messages/MsgInfo[direction=\"Out\"]/schema";
            public const string MessageInSchemaNode = "Messages/MsgInfo[direction=\"In\"]/schema";
            public const string DecisionBranchNameNode = "children/ShapeInfo[shapeType=\"DecisionBranchShape\"]/shapeText";
            public const string OMElementByTypeFormat = "//om:Element[@Type=\"{0}\"]";
            public const string OMNamePropertyNode = "./om:Property[@Name=\"Name\"]/@Value";
            public const string OMIdNode = "./@OID";
            public const string OMElementByIdFormat = "//om:Element[@OID=\"{0}\"]";
            public const string OMConstructMessageRefProperyNode = "./om:Element[@ParentLink=\"Construct_MessageRef\"]/om:Property[@Name=\"Ref\"]/@Value";
            public const string OMParentTypeByIdFormat = "//om:Element[@OID=\"{0}\"]/../@Type";
        }

        /// <summary>
        /// Struct defining the namespace of the orchestration OM XML
        /// </summary>
        private struct OMNamespace
        {
            public const string Prefix = "om";
            public const string Uri = "http://schemas.microsoft.com/BizTalk/2003/DesignerData";
        }

        #endregion
    }
}
